<?php

namespace qore_base\base;
use \qore\util\collections\Configuration;
use \qore\util\collections\Map;
use \qore\util\ClassHelper;
use \qore\error\Exception;
use \qore\error\InvalidArgumentException;
use \Qore;

/**
 * @author Samuel Nogueira <samuel.nogueira.dev@gmail.com>
 */
abstract class Component {
	/**
	 * @var array Array of event handlers in the form of: array($eventName => array($handler1, $handler2, ...))
	 */
	protected $_eventHandlers = array();
	
	/**
	 * @var array
	 */
	protected $_cache = array();
	
	protected static $_instances = array();
	
	/**
	 * Returns a singleton instance of the component
	 * @return Component
	 */
	public static function i() {
		$class = get_called_class();
		$constructors = func_get_args();
		
		return isset(static::$_instances[$class]) ?
			static::$_instances[$class] :
			static::$_instances[$class] = ClassHelper::construct($class, $constructors);
	}

	public function __construct(array $config = array()) {
		$defaultConfig = Qore::app() ? Qore::app()->getComponentsConfig(get_class($this)) : array();
		
		$configCollection = new Configuration(array_merge($defaultConfig, $config));
		$configCollection->applyTo($this);
		$this->init();
	}

	public function init() {}
	
	public function __call($name, $arguments) {
		if (substr($name, 0, 3) == 'get') {
			$property = lcfirst(substr($name, 3));
			return $this->$property;
		}
		elseif (substr($name, 0, 3) == 'set') {
			$property = lcfirst(substr($name, 3));
			return $this->$property = $arguments[0];
		}
		
		throw new Exception("Method '$name' does not exist in ".get_class($this));
	}

	/**
	 * Returns a property value.
	 * Do not call this method. This is a PHP magic method.
	 * @param string $name The property name
	 * @return mixed The property value
	 * @see __set
	 */
	public function __get($name) {
		$getter = "get{$name}";
		
		// If there is a defined getter
		if (method_exists($this, $getter)) return $this->$getter();
		
		// Property does not exist
		throw new \Exception(Qore::t('qore', 'Property "%s.%s" is not defined.', array(get_class($this), $name)));
	}

	/**
	 * Sets value of a component property.
	 * Do not call this method. This is a PHP magic method.
	 * @param string $name the property name
	 * @param mixed $value the property value
	 * @return mixed
	 * @see __get
	 */
	public function __set($name, $value) {
		$setter = "set{$name}";
		
		// If there is a defined setter
		if (method_exists($this, $setter)) return $this->$setter($value);
		
		// If property is read-only
		if ($this->propertyIsReadOnly($name)) throw new CException(Qore::t('qore', 'Property "%s.%s" is read only.', array(get_class($this), $name)));
		
		// Property does not exist
		throw new Exception(Qore::t('qore', 'Property "%s.%s" is not defined.', array(get_class($this), $name)));
	}
	
	/**
	 * @param string $key
	 * @param mixed $getValue Must be a callable function
	 * @throws InvalidArgumentException If $getValue is not callable
	 */
	public function cache($key, $getValue) {
		if (!is_callable($getValue)) throw new InvalidArgumentException(Qore::t('qore', '$getValue must be a callable function'));
		return array_key_exists($key, $this->_cache) ? $this->_cache[$key] : $this->_cache[$key] = $getValue();
	}

	/**
	 * Determines whether a property is defined.
	 * A property is defined if there is a getter or setter method
	 * or if it is defined in the class.
	 * @param string $name The property name
	 * @return boolean Whether the property is defined
	 */
	public function hasProperty($name) {
		return property_exists($this, $name) || method_exists($this, "get{$name}") || method_exists($this, "set{$name}");
	}

	/**
	 * Whether or not the property is read-only
	 * @param string $property The property name
	 * @return boolean
	 */
	public function propertyIsReadOnly($name) {
		return method_exists($this, "get{$name}");
	}

	/**
	 * Checks if a property value is set and is not null.
	 * Do not call this method. This is a PHP magic method that we override
	 * to allow using isset() to detect if a component property is set or not.
	 * @param string $name the property name
	 * @return boolean
	 */
	public function __isset($name) {
		$getter = "get{$name}";
		
		return method_exists($this, $getter) ? $this->$getter() !== null : false;
	}

	/**
	 * Sets a component property to be null.
	 * Do not call this method. This is a PHP magic method that we override
	 * to allow using unset() to set a component property to be null.
	 * @param string $name the property name or the event name.
	 * @throws CException if the property is read only.
	 */
	public function __unset($name) {
		$this->$name = null;
	}

	/**
	 * Returns the list of attached event handlers for an event.
	 * @param string $name The event name
	 * @return array List of event handlers
	 * @throws CException if the event is not defined
	 */
	public function getEventHandlers($name) {
		return isset($this->_eventHandlers[$name]) ? $this->_eventHandlers[$name] : array();
	}
	
	/**
	 * Calls event handlers associated with given trigger
	 * @param string $name Event name
	 * @param mixed §param1, $param2, ... Optional params to pass to handler
	 */
	public function trigger($name) {
		$funcArgs = func_get_args();
		$paramArgs = array_slice($funcArgs, 1);
		
		foreach ($this->getEventHandlers($name) as $handler) {
			call_user_func_array($handler, $paramArgs);
		}
	}
	
}